<?php
namespace Core;

/**
 * View 类中的模板编译方法如下：
 * - _include()：预包含模板
 * - _php()：PHP脚本
 * - _run()：运行一个操作，并将该操作的响应内容插入至当前位置
 * - _ternary()：将“模板表达式”编译为PHP的表达式
 * - _echo()：将“模板变量”编译为 PHP echo 输出
 * - finish()：最后的编译动作
 *
 */
final class View {
	private $content = '';
	private $script = array();
	private $variable = array();

	public function __set($name, $value) {
		$this->assign($name, $value);
	}

	/**
	 * 注册一个模板变量
	 *
	 * @param string $name 变量名称
	 * @param string $value 变量值
	 * @return boolean
	 */
	public function assign($name, $value = null) {
		if (is_array($name) || is_object($name)) {
			foreach ($name as $key => $value) {
				$this->assign($key, $value);
			}
			
			return true;
		}
		
		if (preg_match('/\W/', $name)) {
			return false;
		}
		
		$this->variable[$name] = $value;
		
		return true;
	}

	/**
	 * 将一个模板标签元素的属性列表串解析为属性列表数组。
	 *
	 * @param string &$string 元素的属性列表串
	 * @return array
	 */
	private function attribute(&$string) {
		preg_match_all('/(\w+)\s*=\s*(\"|\')(.*)\\2/Us', $string, $list);
		
		$attribute = array();
		
		foreach ($list[1] as $key => $value) {
			$attribute[strtolower($value)] = $list[3][$key];
		}
		
		return $attribute;
	}

	/**
	 * 将“模板表达式”解析为“PHP表达式”。
	 *
	 * 典型示例：
	 * 1、{$mobile}?{$name}?:{$email}?{$username}:-
	 * 2、{$city}
	 * 3、{$_province.{$province}}
	 * 4、{$_province.{$province}}{$_city.{$city}}
	 * 5、A{$_province.{$province}}B{$_city.city{$city}}C
	 *
	 * @param string $expression
	 * @return string
	 */
	private function expression($expression) {
		if (strpos($expression, '?') != false) {
			while (preg_match('/\?([^\?\:]*(?:\?|$))/', $expression, $tmp)) {
				$expression = str_replace($tmp[0], "?:{$tmp[1]}", $expression);
			}
			
			$expr = $end = '';
			
			$expression = preg_split('/(\?\:|\?|\:)/', $expression, 0, PREG_SPLIT_DELIM_CAPTURE);
			
			for ($key = 0; $key < count($expression); $key += 2) {
				$expr .= self::expression($expression[$key]) . (isset($expression[$key + 1]) ? $expression[$key + 1] : '');
				
				if (substr($expr, -1) == ':') {
					$expr .= '(';
					$end .= ')';
				}
			}
			
			return $expr . $end;
		}
		
		while (preg_match('/\{\$([^\{\}]+)\}/', $expression, $variable)) {
			$var = explode('.', $variable[1]);
			
			$name = '$' . array_shift($var);
			
			foreach ($var as $v) {
				$name .= (is_numeric($v) ? "[$v]" : "['$v']");
			}
			
			// 之所以将“{”和“}”分别替换为“\r”和“\n”是为了配合当前 while 语句在处理当前表达式串时能够从内到外逐层处理
			$expression = str_replace($variable[0], "\r$name\n", $expression);
		}
		
		$expression = str_replace("\n", ".'", str_replace("\r", "'.", $expression));
		
		return preg_replace("/''\\.|\\.''/", '', "'" . $expression . "'");
	}

	/**
	 * 预包含指定的模板文件，即将其中的 <include> 标签替换为所指定的模板文件内容。
	 *
	 * <include> 标签中必须有 html 属性，其值即包含的外部模板
	 *
	 * - 指定主题，指定控制器，指定操作，例如：<include href="Default/Member/Login" />
	 * - 默认主题，指定控制器，指定操作，例如：<include href="Member/Login" />
	 * - 默认主题，当前控制器，指定操作，例如：<include href="Login" />
	 *
	 * 注意：
	 *
	 * 由于 href 属性值是一个模板文件描述，而在 Linux 系统中，目录和文件名是大小写敏感的，所以在书写模板文件描述时应格外注意
	 *
	 * @param string &$content
	 * @return boolean
	 */
	private function _include(&$content) {
		if (!preg_match_all('#<include([^>]+)(?:(?:/>)|(?:>\s*</include>))#i', $content, $list)) { // 从给定的字符串内容中找出所有<include /> 模板标签
			return true;
		}
		
		foreach ($list[1] as $key => $include) {
			$include = $this->attribute($include);
			
			$html = $this->html($this->template($include['href']));
			
			is_file($html) || trigger_error('Not include the template file does not exist', E_USER_ERROR);
			
			$content = str_replace($list[0][$key], file_get_contents($html), $content);
		}
		
		return $this->_include($content);
	}

	/**
	 * 将模板中的 PHP 原生脚本内容替换为模板变量
	 *
	 * 因为模板变量以 $ 开头，而在处理模板时，为避免 <?php ?> 中间的PHP变量被错误替换，所以在处理模板之前应首先将各PHP脚本段取出并替换为占位符，待其它模板处理动作均完成后再恢复各PHP脚本段
	 *
	 * @return boolean
	 */
	private function _php() {
		if (!preg_match_all('/<\?php.+\?>/Usi', $this->content, $list)) {
			return true;
		}
		
		foreach ($list[0] as $id => $script) {
			$label = "{HHO_VIEW_PHP_$id}";
			
			$this->script[$label] = $script;
			
			$this->content = str_replace($script, $label, $this->content);
		}
		
		return true;
	}

	/**
	 * 将模板中的 <run> 标签编译为对“HHO::run()”的调用
	 *
	 * @return boolean
	 */
	private function _run() {
		if (!preg_match_all('#<run([^>]+)(?:(?:/>)|(?:>\s*</run>))#i', $this->content, $list)) {
			return true;
		}
		
		foreach ($list[1] as $key => $run) {
			$run = $this->attribute($run);
			
			$controller = $this->expression($run['controller']) ?  : 'Core\HHO::$moudle';
			
			$action = $this->expression($run['action']);
			
			$this->content = str_replace($list[0][$key], "<?php Core\HHO::run({$controller},{$action}); ?>", $this->content);
		}
		
		return true;
	}

	/**
	 * 将“模板表达式”编译为PHP表达式，表达式通常有多变量连接、三元运算以及赋值
	 *
	 * - 注意：不支持多层嵌套
	 *
	 * 典型示例：
	 * - {!{$mobile}?-!}
	 * - {!{$mobile}?:-!}
	 * - {!{$mobile}?{$name}?-!}
	 * - {!{$mobile}?{$name}:-!}
	 * - {!{$mobile}?{$name}?:-!}
	 * - {!{$mobile}?{$name}?:{$email}!}
	 * - {!{$mobile}?{$name}?:{$email}?{$username}!}
	 * - {!{$mobile}?{$name}?:{$email}?{$username}:-!}
	 * - {!{$test}={$mobile}?{$name}?:{$email}?{$username}:-!}
	 *
	 * @return boolean
	 */
	private function _ternary() {
		preg_match_all('/\{!(.+)!\}/U', $this->content, $expression);
		
		foreach ($expression[1] as $key => $value) {
			if (preg_match('/^\{(\$\w+)\}=(.+)$/', $value, $tmp)) {
				$expr = "{$tmp[1]}=" . self::expression($tmp[2]);
			} else {
				$expr = "echo " . self::expression($value);
			}
			
			$this->content = str_replace($expression[0][$key], "<?php {$expr}; ?>", $this->content);
		}
		
		return true;
	}

	/**
	 * 将模板表达式输出编译为PHP表达式，并 echo 输出
	 *
	 * 为避免长表达式被误拆为短表达式，应首先处理长表达式，再处理短表达式
	 *
	 * @return boolean
	 */
	private function _echo() {
		preg_match_all('/\{\$(?:[\.\w]+|(?R))*\}/', $this->content, $expression);
		
		$length = array();
		
		foreach ($expression[0] as $value) {
			$length[] = strlen($value);
		}
		
		arsort($length);
		
		foreach ($length as $key => $value) {
			$this->content = str_replace($expression[0][$key], '<?php echo ' . $this->expression($expression[0][$key]) . '; ?>', $this->content);
		}
		
		return true;
	}

	/**
	 * 完成最后阶段的编译工作
	 *
	 * @return boolean
	 */
	private function finish() {
		// 恢复 $this->_php() 缓存的PHP脚本段
		foreach ($this->script as $key => $value) {
			$this->content = str_replace($key, $value, $this->content);
		}
		
		// 将多余PHP脚本标记去掉
		$this->content = preg_replace('/;?\s*\?>\s*<\?php/s', '; ', $this->content);
		
		return true;
	}

	/**
	 * 将指定模板编译并创建至PHP脚本缓存文件中，成功则返回PHP缓存文件描述，失败则返回false
	 *
	 * @param array $template
	 * @return boolean
	 */
	private function compile($template) {
		$phps = HHO_APP_PATH . '/Runtime/View/' . implode('!', $template) . '.php';
		
		if (!HHO::$config['debug'] && is_file($phps)) {
			return $phps;
		}
		
		$htmls = $this->html($template);
		
		is_file($htmls) || trigger_error('Template file does not exist', E_USER_ERROR);
		
		$this->content = file_get_contents($htmls);
		
		if ($this->content === false) {
			return false;
		}
		
		$this->content = preg_replace('/<!--.*-->/Us', '', $this->content);
		
		if (!$this->_include($this->content)) { // 最先将外部模板包含至当前模板中
			return false;
		}
		if (!$this->_php()) {
			return false;
		}
		if (!$this->_run()) {
			return false;
		}
		if (!$this->_ternary()) {
			return false;
		}
		if (!$this->_echo()) {
			return false;
		}
		if (!$this->finish()) {
			return false;
		}
		
		if (!file_put_contents($phps, $this->content, LOCK_EX)) {
			return false;
		} elseif (!file_put_contents($phps, php_strip_whitespace($phps), LOCK_EX)) {
			return false;
		}
		
		return $phps;
	}

	/**
	 * 获得规整后的模板描述。
	 *
	 * @param string $template
	 * @return array
	 */
	private function html($template) {
		return HHO::$config['view.root'] . '/' . implode('/', $template) . '.html';
	}

	/**
	 * 将指定的模板描述字符串拆分至数组中，并更改 $template 参数值，返回指定模板对应的 HTML 文件描述。
	 *
	 * @param string $template
	 * @return string
	 */
	private function template($template) {
		$template = trim(str_replace('\\', '/', $template), '/');
		
		$info = explode('/', $template);
		
		if (!$template || count($info) > 3) {
			trigger_error('Unknown template name. (Template is ' . ($template ?  : 'Void'), E_USER_ERROR);
		}
		
		// theme/controller/action，即指定主题的指定控制器的指定操作
		if (count($info) == 3) {
			return $info;
		}
		
		// controller/action，即默认主题的指定控制器的指定操作
		if (count($info) == 2) {
			return array(HHO::$config['view.default'], $info[0], $info[1]);
		}
		
		// action，即默认主题的当前控制器的指定操作
		return array(HHO::$config['view.default'], HHO::$controller, $info[0]);
	}

	/**
	 * 输出响应内容
	 *
	 * @param string $template 模板文件，默认为默认主题下的当前控制器当前操作的模板文件
	 * @return boolean
	 */
	public function display($template) {
		return $this->fetch($template, FALSE);
	}

	/**
	 * 将模板处理结果内容输出至客户端或返回至方法调用处
	 *
	 * @param string $_HHO_VIEW_TEMPLATE 模板文件
	 * @param boolean $_HHO_VIEW_FETCH 返回模板处理结果内容，否则输出模板处理结果至客户端
	 * @return string|boolean
	 */
	public function fetch($_HHO_VIEW_TEMPLATE, $_HHO_VIEW_FETCH = TRUE) {
		$_HHO_VIEW_X = $this->template($_HHO_VIEW_TEMPLATE);
		
		$_HHO_VIEW_PHPS = $this->compile($_HHO_VIEW_X);
		
		// 向模板中注册 $_THEME、$_CONTROLLER、$_ACTION 三个变量。
		list($_THEME, $_CONTROLLER, $_ACTION) = $_HHO_VIEW_X;
		
		require HHO::$config['view.root'] . '/View.php';
		
		// 向模板中注册在控制器中所声明的变量。
		extract($this->variable);
		
		if ($_HHO_VIEW_FETCH) {
			ob_start();
			ob_implicit_flush(0);
			
			require $_HHO_VIEW_PHPS;
			
			return ob_get_clean();
		}
		
		require $_HHO_VIEW_PHPS;
		
		return TRUE;
	}
}